package med;

import java.net.URL;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import med.config.Config;
import med.extract.Extractor;

import med.utils.Reader;

public class Application {

	private SVMModule svmModule;
	
	public Application()
	{
		svmModule = new SVMModule();
	}
	
	public Application(String configFile) {
		svmModule = new SVMModule(configFile);
	}

	public static void main(String[] args) {
		Config cfg = Config.getInstance();
		cfg.parseArgs(args);
		
		final String trainDir = args[0];
		final String testDir = args[1];
		String configFile = null;
		if(args.length == 3){
			configFile = args[2];
			}
		try {
			Application app = null;
			if(configFile != null)
				app = new Application(configFile);
			else
				app = new Application();
			app.train(Reader.getTextFromDirectory(trainDir));
			List<String> text;
			if (testDir.startsWith("-url:")) {
				text = new Extractor().getText(new URL(testDir.substring(5)),
						cfg.getMaxDepthParam(), cfg.isUseStemmerParam());
			} else {
				text = Reader.getTextFromDirectory(testDir);
			}
			app.classify(text);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}
	
	private void train(List<String> text) {
		VectorIterator it = VectorIterator.getIterator(text, Config
				.getInstance().getVectorLengthParam(), false);

		List<Element> elements = new LinkedList<Element>();

		while (it.hasNext()) {
			Vector vt = it.next();
			Element elem = tryGetElement(vt);
			if (elem != null) {
				System.out.println(vt.toString());
				elements.add(elem);
			}
		}
		svmModule.trainSVM(elements);
	}

	private void classify(List<String> text) {
		if(!svmModule.isTrained()) {
			throw new IllegalStateException("classifier is not trained");
		}
		
		Stats stats = new Stats();
	
		VectorIterator it = VectorIterator.getIterator(text, Config
				.getInstance().getVectorLengthParam(), false);
		
		while (it.hasNext()) {
			Vector vt = it.next();
			Element embResult = tryGetElement(vt);
			Element svmResult = svmModule.classification(vt);
	
			if (svmResult == null) {
				// svm failed to classify an element
				if (embResult == null) {
					// true negative
					++stats.getMain().tn;
				} else {
					// false negative
					++stats.getMain().fn;
					++stats.forClass(embResult.getType()).fn;
				}
			} else {
				// svm classified an element
				++stats.getMain().ttl;
				++stats.forClass(svmResult.getType()).ttl;
				if (embResult == null) {
					// false positive (or no test info)
					System.out.println("\tfalse positive: " + svmResult);
					++stats.getMain().fp;
					++stats.forClass(svmResult.getType()).fp;
				} else {
					if(svmResult.getClass().equals(embResult.getClass())) {
						// true positive (actual hit)
						++stats.getMain().tp;
						++stats.forClass(svmResult.getType()).tp;
						System.out.println("true positive: " + svmResult);
					} else {
						// false positive (classified as wrong class)
						System.out.println("\tfalse positive : " + svmResult
								+ "\t(expected: " + embResult.getType().name()
								+ ")");
						++stats.getMain().fp;
						++stats.forClass(svmResult.getType()).fp;
					}
				}
			}
		}

		stats.printSummary(true);
	}
	
	private static Element tryGetElement(Vector vt) {
		Element elem = null;
	
		String tag = vt.getWord(Config.getInstance().getTagPositionParam());
	
		if (tag != null && tag.contains("|class|")) {
			if (tag.contains("|acceptDate|")) {
				elem = new AcceptanceDate(vt);
			} else if (tag.contains("|camRdy|")) {
				elem = new CameraReadyDate(vt);
			} else if (tag.contains("|committee|")) {
				elem = new Committee(vt);
			} else if (tag.contains("|confDate|")) {
				elem = new ConferenceDate(vt);
			} else if (tag.contains("|place|")) {
				elem = new ConferencePlace(vt);
			} else if (tag.contains("|name|")) {
				elem = new ConfernceName(vt);
			} else if (tag.contains("|nameAbbr|")) {
				elem = new NameAbbreviation(vt);
			} else if (tag.contains("|subDate|")) {
				 elem = new PaperSubmissionDate(vt);
			} else if (tag.contains("|abstractDate|")) {
				elem = new PaperSubmissionAbstract(vt);
			} else if (tag.contains("|registDate|")) {
				elem = new RegistrationDate(vt);
			}
		}
		return elem;
	}

	private static class Stats {
		
		private static class Entry {
			int ttl;
			int tp;
			int tn;
			int fp;
			int fn;
		}
		
		Entry mainStats = new Entry();
		Map<Type, Entry> classStats = new HashMap<Type, Entry>();
		
		public Stats() {
			reset();
		}
		
		public void reset() {
			mainStats = new Entry();
			classStats.clear();
			for(Type type : Type.values()) {
				classStats.put(type, new Entry());
			}
		}
		
		public Entry forClass(Type type) {
			return classStats.get(type);
		}
		
		public Entry getMain() {
			return mainStats;
		}
		
		public void printSummary(boolean withClassStats) {
			System.out.println("\nResult:\n");
			if(withClassStats) {
				for(Map.Entry<Type, Entry> kv : classStats.entrySet()) {
					print(kv.getKey().name(), kv.getValue());
				}
			}
			print("TOTAL", mainStats);
		}
		
		private void print(String name, Entry entry) {
			StringBuilder sb = new StringBuilder();
			sb.append(name + ":\n");
			sb.append("\tttl: %d\n");
			sb.append("\ttrue positives: %d\n");
			sb.append("\tfalse positives: %d\n");
			sb.append("\ttrue negatives: %d\n");
			sb.append("\tfalse negatives: %d\n");
			sb.append("\tprecision: %f");
			System.out.println(String.format(sb.toString(), entry.ttl, entry.tp, entry.fp, entry.tn, entry.fn,
					(float) entry.tp / (entry.ttl + entry.fn)));
			System.out.println("");
			System.out.flush();
		}
	}
}
